package org.stragma.apply.modules

import org.kiama.rewriting._
import org.kiama.rewriting.Rewriter.{all,attempt,bottomup,fail,innermost,id,rulefs,Strategy,Term,topdown,repeat,topdownS,dontstop}

// Import the required signatures, these are obtained from running sig2scala on the Stratego signatures
import org.stragma.apply.modules.ApplyStrategies._
import org.stragma.apply.signatures.Apply._
import org.stragma.apply.signatures.Conversions._
import org.stragma.apply.util._
import org.stragma.apply.util.Math._

import org.stragma.util.ListManager._

object EnsugarC extends TermInputOutput {

	/*
	 * Helper for use through Java
	 */
	def Apply_EnsugarCHelper(term : Term) : Term = {
		(Apply_EnsugarC(term)).get
	}

	/*
	 * This helper method executes Apply_EnsugarCHelper n times and returns
	 * the very last result the caller
	 */
	def Apply_EnsugarCHelper(term : Term, n : scala.Int) : Term = {
		var result : Term = scala.None
		for(i <- 0 until n) {
			result = (Apply_EnsugarC(term)).get
			println(result)
		}
		
		return result
	}
	
	/*
	 * This helper method executes applyDesugar n times and returns
	 * the very last result to the caller
	 */
	def Apply_EnsugarCHelper(file : String, n : scala.Int) : Term = {
		val iTerm = loadTermFromFile(file)
		Apply_EnsugarCHelper(iTerm, n)
	}
	
	/*
	 * Port of Strategy :
	 * Apply_EnsugarC =
     *			topdown( repeat(EnSugarType)
             			; repeat(EnSugarInc)
             			// ; repeat(EnSugarFor)
           				)
    					; bottomup(try(array-procedure))
	 */
	def Apply_EnsugarC : Strategy = {
		topdown(repeat(EnSugarType) <* repeat(EnSugarInc)) <* bottomup(attempt(array_procedure))
	}

	/*
	 * Port of strategy ->
	 * convert-array-dimensions =
    				[ ?Range(Int(lo),Int(hi)) | ?more ]
    				; ![ Int(<addS>("1",<subtS>(hi,lo))) | <convert-array-dimensions>more ]

  					convert-array-dimensions = []
  					convert-array-dimensions = ?[""] ; ![]
	 */
	def convert_array_dimensions : Strategy = {
		rulefs {
			case ListTRangeToTRangeList(Nil) 							=> Nil
			case ListTRangeToTRangeList(Range2(lo:Int,hi:Int) :: Nil) 	=> List(addS(Int("1"),subtS(hi,lo)))
			case ListTRangeToTRangeList(Range2(lo:Int,hi:Int) :: a) 	=>
				val rang = List(addS(Int("1"),subtS(hi,lo)),convert_array_dimensions(ListTRangeToTRangeList(a)).get)
				
				// Unlike in the Stratego version in Kiama we need to remove nested Lists which are added by the recursive function
				flattenTerm(rang)
		}
	}
	
	/*
	 * Port of strategy
	 * array-procedure =
     *		?Procedure(name, args, decs, stmts, ename)
     *		; {| ArrayAccess :   Procedure(id, array-declarations, id, id, id)
     *                                  ; Procedure(id, id, id, array-use, id) |}
	 */
	def array_procedure : Strategy = {
		rulefs {
			case Procedure(name, args, decs, stmts, ename) =>
			
				// TODO: I am not sure how this works?
				var mod_args = args
				if (array_declarations(args).isDefined)
					mod_args = (array_declarations(args).get).asInstanceOf[TFunctionArgs]
				
				// This cast appears necessary because Kiama treats all re-written vals as Terms which is Any
				// therefore we need to case this value back to what we expect it to be
				var mod_stmts = stmts
				if (array_use(stmts).isDefined)
					mod_stmts = (array_use(stmts).get).asInstanceOf[TStatements]
					
				Procedure(name,mod_args,decs,mod_stmts,ename)
		}
	}
	
	/*
	 * Port of Strategy -> array-declarations = bottomup(try(array-declaration))
	 */
	def array_declarations : Strategy = {
		bottomup(attempt(array_declaration))
	}

	/*
	 * Port of rule ArrayAccess : initial defined to return the original term unchanged, however if array_declaration passes
	 * then the Array access is redefined
	 */
	var ArrayAccess : Strategy = {
		id
	}
	
	/*
	 * Port of strategy to code references to arrays - assume C conversion of type already done.
  	 * array-declaration = 
     * 		?CArgument(t,CArray(CAccess(a,Var(v)),d)) ; debug
     *		; rules ( ArrayAccess : Func(Var(v),s) -> Subscript(Var(v),s) )
	 */
	def array_declaration : Strategy = {
		rulefs {
			// If the match is successful then go on to define the ArrayAccess dynamic rewrite rule
			case CArgument(t,TTypeToTArgType(CArray1(CAccess(a,v),d))) =>
			
				// Define a new rule
				ArrayAccess = rulefs {
					case Func(v : Var, s) 	=> TVarToTScalarVar(Subscript(v,s))
				}
				
				// Return the original term unchanged
				id
				
			case CArgument(t,TTypeToTArgType(CArray(TArgTypeToTType(CAccess(a,v)),d))) =>
				
				// Define a new rule
				ArrayAccess = rulefs {
					case Func(v : Var, s) 	=> TVarToTScalarVar(Subscript(v,s))
				}
				
				// REturn the original term unchanged
				id
		}
	}
		
	/*
	 * Port of strategy array-use = topdownstopping(ArrayAccess)
	 */
	def array_use : Strategy = {
		topdownstopping(ArrayAccess)
	}

	/*
	 * Please note that these rules all work because of the magical
	 * implicit conversion functions defined in org.stragma.apply.util.Conversions
	 */
	def EnSugarType : Strategy = {
		rulefs {
  			// EnSugarType  : Argument(Var(x),t)          -> CArgument(t,Var(x))
  			// EnSugarType  : CArgument(Access(a,t),x)    -> CArgument(t,CAccess(a,x))
  			// EnSugarType  : CArgument(Array(d,t),x)     -> CArgument(t,CArray(x,d))
  			// EnSugarType  : CArgument(Array(d,t),x)     -> CArgument(t,CArray(x,<convert-array-dimensions>d))
  			// EnSugarType  : CArgument(Type(t),x)        -> CArgument(t,x)
  			// border is discarded for C typing
  			// EnSugarType  : CArgument(Border(t,e),x)    -> CArgument(t,x)
  			// EnSugarType  : CArgument(Border(t,e),x)    -> CArgument(t,CBorder(e,x))
  			// EnSugarType  : CArgument(FunctionPtr,x)    -> CFunctionPtr(x)
  			// EnSugarType  : Declaration(Var(x),t)       -> CDeclaration(t,Var(x))
  			// EnSugarType  : CDeclaration(Array(d,t),x)  -> CDeclaration(t,CArray(x,d))
  			// EnSugarType  : CDeclaration(Type(t),x)     -> CDeclaration(t,x)
			case Argument(x : Var,t)								=> CArgument(t,x)
			case CArgument(Access(a,t),x) 							=> CArgument(t,CAccess(a,x))

			case CArgument(TTypeToTArgType(Array(d,t)),x) 			=>
				val conv_res : List[TExp] = (convert_array_dimensions(d).get).asInstanceOf[List[TExp]]
				CArgument(t,CArray1(x,conv_res))
				
			case CArgument(t:Type,x) 								=> CArgument(t.arg0,x)
			case CArgument(TTypeToTArgType(Border(t,e)),x) 			=> CArgument(t,CBorder(e,x))
			case CArgument(e:FunctionPtr,x)							=> CFunctionPtr(x)
			case Declaration1(Var(x),t)								=> CDeclaration(t,TVarToTType(Var(x)))
			case CDeclaration(Array(d,t),x)							=> CDeclaration(t,CArray(x,d))
			case CDeclaration(t:Type,x)								=> CDeclaration(t.arg0,x)
		}
	}
	
	/*
	 * Port of EnsugarInc strategy
	 */
	def EnSugarInc : Strategy = {
		rulefs {
			// EnSugarInc   : |[ x := x + i ; ]|          -> CIncrementStmt(x,Int(i))
  	 		// EnSugarInc   : |[ x := x - i ; ]|          -> CIncrementStmt(x,Int(k))   where <subtS>("0",i) => k
  	 		// EnSugarInc   : |[ x @:= x + i ]|           -> CIncrementExp(x,Int(i))
  	 		// EnSugarInc   : |[ x @:= x - i ]|           -> CIncrementExp(x,Int(k))   where <subtS>("0",i) => k
  	 		// EnSugarInc   : CIncrementExp(x,Int("1"))   -> CIncrementExp(x)
			case Assign(TVarToTScalarVar(x),Add1(y,i : Int)) if (x==y)	=> CIncrementStmt(x,i)
			case Assign(TVarToTScalarVar(x),Sub1(y,i : Int)) if (x==y) 	=> CIncrementStmt(x,subtS(Int("0"),i))
			case CAssign(x,Add1(y,i : Int)) if (x==y) 					=> CIncrementExp(x,i)
			case CAssign(x,Sub1(y,i : Int)) if (x==y) 					=> CIncrementExp(x,subtS(Int("0"),i))
			case CIncrementExp(x,Int("1"))   							=> CIncrementExp1(x)
		}
	}
}